package task

import (
	"bytes"
	"crypto/md5"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"math"
	"math/rand"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"youbei/model"

	"time"

	"gitee.com/goshark/gziprsaes/gziprsaes"
	"gitee.com/johng/gf/g/encoding/gjson"
	"gitee.com/johng/gf/g/net/ghttp"
	"gitee.com/johng/gf/g/net/gsmtp"
	"gitee.com/johng/gf/g/os/gcron"
	"gitee.com/johng/gf/g/os/glog"
	_ "github.com/denisenkom/go-mssqldb"
)

const (
	DBFILE       = "MyDB"
	server       = "http://idcyw.cn"
	serverstatic = "http://idcyw.cn"
)

type Response struct {
	Code   int         `json:"code"`
	Result interface{} `json:"result"`
	Msg    string      `json:"msg"`
}

//任务
type Task struct {
	Uuid        string `json:"uuid,omitempty"`      //任务uuid
	Intime      int64  `json:"intime,omitempty"`    //任务录入时间
	Starttime   int64  `json:"starttime,omitempty"` //任务开始时间
	Interval    string `json:"interval,omitempty"`  //任务周期
	Deleted     bool   `json:"deleted,omitempty"`   //是否删除(软删除)
	Status      int    `json:"status"`              //任务执行状态,0:未执行,1:执行中,2:执行完成
	Name        string `json:"name,omitempty"`      //任务名称
	Useruuid    string `json:"useruuid,omitempty"`  //用户uuid
	Repeatcount int    `json:"repeatcount"`         //执行次数.0:重复执行,n>0,执行n次
	Database    string `json:"database,omitempty"`  //备份数据库
	Category    string `json:"category,omitempty"`  //数据库类别,mysql,mssql
	Version     string `json:"version,omitempty"`   //备份版本
}

var rootpath string = GetCurrentDirectory()

/*
	执行任务
*/
func (t *Task) goexec() {
	var tongbu sync.WaitGroup
	go func() {
		tongbu.Add(1)
		nowtime := time.Now().Unix()
		if t.Starttime > 0 && t.Starttime > nowtime {
			time.Sleep(time.Duration((t.Starttime - nowtime)) * time.Second) //开始时间-当前时间 = 段时间,阻塞一段时间
			go Tasking(t)
		}
		if string([]rune(t.Interval)[1:]) == "M" {
			if err := gcron.Add("@monthly", func() { Tasking(t) }, t.Uuid); err != nil {
				glog.Cat("error").Error(err)
			}

		} else {
			if err := gcron.Add("@every "+t.Interval, func() { Tasking(t) }, t.Uuid); err != nil {
				glog.Cat("error").Error(err)
			}

		}
		tongbu.Done()
	}()

	tongbu.Wait()
	taskdb := model.NewDB("task")
	t.Status = 1 //修改任务状态为运行
	taskb, err := gjson.Encode(t)
	if err != nil {
		glog.Cat("error").Error(err)
	}
	taskdb.Data(taskb).WhereKey(t.Uuid).Update()
}

/*
	添加新任务name ,st starttime,rep repeatcount,ivl interval ,dbs database;
*/

func NewTask(name string, st int64, rep int, ivl string, dbs string, useruuid string, datatypes ...string) (bool, error) {
	if useruuid == "" {
		glog.Cat("error").Error("该任务未绑定用户")
		return false, errors.New("该任务未绑定用户")
	}
	task := new(Task)
	task.Name = name
	task.Uuid = Generate_uuid()
	task.Useruuid = useruuid
	task.Intime = time.Now().Unix()
	task.Status = 1
	task.Interval = ivl
	if st <= 0 {
		task.Starttime = task.Intime
	} else {
		task.Starttime = st
	}
	if rep <= 0 {
		task.Repeatcount = 0
	}

	if len(datatypes) > 0 {
		if len(datatypes) > 1 {
			task.Category = datatypes[0]
			task.Version = datatypes[1]
		} else {
			task.Category = datatypes[0]
		}
	}
	task.Database = dbs
	//任务写入数据库
	task_b, err := gjson.New(task).ToJson()
	if err != nil {
		glog.Cat("error").Error(err)
		return false, err
	}

	taskdb := model.NewDB("task")

	if err != nil {
		glog.Cat("error").Error(err)
		return false, err
	}
	if taskdb.WhereKey(task.Uuid).Data(task_b).Insert() {
		task.goexec()
		return true, nil
	} else {
		glog.Cat("error").Error("任务记录数据库失败")
		return false, errors.New("任务记录数据库失败")
	}

}

/*
	任务列表
*/
func Tasklist() [][]byte {
	var tlists [][]byte
	usermap := GetLocalUser()
	if usermap == nil {
		return tlists
	}
	taskdb := model.NewDB("task")
	tlist := taskdb.Where(gjson.New([]map[string]interface{}{{"name": "useruuid", "value": usermap["uuid"], "op": "="}}).ToArray()).Select()

	for _, v := range tlist {
		tlists = append(tlists, v)
	}
	return tlists
}

/*
	生成uuid
*/
func Generate_uuid() string { //uuid由 当前时间戳+random组成
	rand.Seed(time.Now().Unix()) //随机种子
	md := strconv.Itoa(rand.Intn(99)) + strconv.FormatInt(time.Now().Unix(), 10)
	data := []byte(md)
	has := md5.Sum(data)
	md5str := fmt.Sprintf("%x", has) //将[]byte转成16进制
	return md5str
}

/*
statement:开始扫描任务库
参数:uuid 用户uuid
*/
func Startask(uuid string) {
	//step1. Get the data list and the field state is not failed of the local database(nosql for gkvdb).
	taskdb := model.NewDB("task")
	tlist_run := taskdb.Where(gjson.New([]map[string]interface{}{{"name": "useruuid", "value": uuid, "op": "="}}).ToArray()).Select()
	for _, t := range tlist_run {
		task := new(Task)
		if err := gjson.DecodeTo(t, &task); err == nil {
			if gcron.Search(task.Uuid) != nil {
				continue
			}
			task.Starttime = GetCurrentUnix(task.Starttime)
			starttime := time.Unix(task.Starttime, 0)
			if time.Now().After(starttime) {
				task.Starttime = starttime.AddDate(0, 0, 1).Unix()
			}
			glog.Cat("runtime").Printf("任务--名称(%s)--数据库(%s)开始时间已过期，将自动推迟到当天时间未过期的时间点于 %s 开始执行;\r\n", task.Name, task.Database, time.Unix(task.Starttime, 0).Format("2006-01-02 15:04:05"))
			task.goexec()
		}
	}
}

/*
	获取当前日期某个时间点
*/
func GetCurrentUnix(t int64) int64 {
	if t != 0 {
		parses, _ := time.ParseInLocation("2006_01_02 15_04_05", time.Now().Format("2006_01_02")+" "+strings.Split(time.Unix(t, 0).Format("2006_01_02 15_04_05"), " ")[1], time.Local)
		return parses.Unix()
	} else {
		return time.Now().Unix()
	}

}

/*
	结束任务
*/
func Endtask() {
	//step1. Get the data list and the field state is not failed of the local database(nosql for gkvdb).
	taskdb := model.NewDB("task")
	tlist_run := taskdb.Where(gjson.New([]map[string]interface{}{{"name": "status", "value": 1, "op": "="}}).ToArray()).Select()

	for k, t := range tlist_run {
		task := new(Task)
		gjson.DecodeTo(t, &task)
		task.Status = 0
		t1, _ := gjson.Encode(task)
		taskdb.WhereKey(k).Data(t1).Update()
	}
}

/*
	任务逻辑
	step.验证加密
	step1.加密处理数据库数据并生成文件
	step2.验证access_token是否过期,过期重新向GS服务器获取
	step3.向GS服务器发送请求，获取上传验证码及文件服务器信息，携带参数{access_token,sourceFileSize,sourceFileMd5}
	step4.向FS服务器发送请求，开始上传文件。携带参数{access_token,sourceFileSize,sourceFileMd5,uploadCode,realFileSteam}
	step5.上传完成后向GS服务器发送成功事件请求，携带参数{access_token,sourceFileSize,sourceFileMd5,}

*/
var taskrwmutex sync.RWMutex

func backup(task *Task, config *Config, dbfile string) error {

	var c string
	if task.Category == "mysql" {
		c = "mysqldump --single-transaction -u" + config.Dbuser + " -p" + config.Dbpass + " " + task.Database + " > ../" + dbfile
	} else {
		batfile := "bkp.bat"
		c = fmt.Sprintf(`%s %s %s %s %s`, batfile, config.Dbuser, config.Dbpass, task.Database, rootpath+"/"+dbfile)
	}

	cmd0 := exec.Command("CMD", "/C", c)

	cmd0.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
	err := cmd0.Start()

	if err != nil {
		return err
	}

	return cmd0.Wait()
}
func Tasking(task *Task) {
	taskrwmutex.Lock()
	defer taskrwmutex.Unlock()

	glog.Cat("runtime").Printf("任务--名称(%s)--数据库(%s)--周期(%s)--创建者(%s)--UUID(%s)--执行时间%s\r\n", task.Name, task.Database, task.Interval, task.Useruuid, task.Uuid, time.Now().Format("2006-01-02 15:04:05"))

	config_b := model.NewDB("dbconfig").Where(gjson.New([]map[string]interface{}{{"name": "category", "value": task.Category, "op": "="}, {"name": "version", "value": task.Version, "op": "="}}).ToArray()).Select()
	var data_b []byte

	for _, v := range config_b {
		data_b = v
		break
	}

	if len(data_b) == 0 {
		glog.Cat("error").Errorf("任务--名称(%s)--数据库(%s)未获取到数据库配置信息\r\n", task.Name, task.Database)
		return
	}

	config := new(Config)
	err := gjson.DecodeTo(data_b, &config)
	if err != nil {
		glog.Cat("error").Error(err)
		return
	}
	if err := os.Chdir(rootpath + "/sqltool"); err != nil {
		glog.Cat("error").Error(err)
		return
	}
	var dbfile string
	if task.Category == "mysql" {
		dbfile = task.Database + "_" + time.Now().Format("01021504") + ".sql"
	} else {
		dbfile = task.Database + "_" + time.Now().Format("01021504") + ".bak"
	}

	if err := backup(task, config, dbfile); err != nil {
		os.Chdir(rootpath)
		glog.Cat("error").Error(err)
		return
	}

	//文件压缩和上传
	if err := os.Chdir(rootpath); err != nil {
		glog.Cat("error").Error(err)
		return
	}

	var filepath string = dbfile
	defer RemoveClientFile(filepath)

	targetfile, err := os.Open(rootpath + "/" + filepath)

	if err != nil {
		glog.Cat("error").Error(err)
		return

	}
	targetfileinfo, err := targetfile.Stat()
	if err != nil {
		glog.Cat("error").Error(err)
		return
	}
	usermap := GetLocalUser()
	datamap := make(map[string]interface{})
	datamap["source_file"] = targetfileinfo.Size() / 1024 //byte / 1024 = kb
	datamap["file_md5"] = getfilemd5(filepath)
	datamap["access_token"] = usermap["access_token"]
	//datamap["useruuid"] = usermap["uuid"]
	resp, e := Postserver(server+"/api/fileUpload/index", datamap)

	if e != nil {
		glog.Cat("error").Error(e)
		return
	}

	if resp.Code != 0 {
		if resp.Code == 4 {
			go sendmail(usermap["username"].(string), `<h1>尊敬的用户</h1><h2>您的剩余容量已使用完,将导致数据无法正常备份,请及时扩容！<a href="`+server+`">请点击查看如何扩大容量？</a></h2>`)
		}
		glog.Cat("error").Errorf("本次任务备份失败,失败原因,%v\n", resp)
		return
	}
	//fmt.Println("第一次请求返回", resp)

	result := gjson.New(resp.Result)
	filedata, keyfile, err := CompresswithCipher(filepath)
	if err != nil {
		glog.Cat("error").Error(err)
		return
	}

	if err := result.Set("cipherfile_md5", getdatamd5(filedata)); err != nil {
		glog.Cat("error").Error(err)
		return
	}

	if len(filedata) >= 5*1024*1024 {
		block := make([]byte, 5*1024*1024) //定义块数据容器
		sdr := bytes.NewReader(filedata)   //读取目标数据,source_data_reader
		total_size := float64(sdr.Len() / 1024 / 1024)
		totalBlobNum := math.Ceil(math.Ceil(total_size) / 5)
		glog.Cat("runtime").Printf("当前文件总大小为%vM\n,按5M可分为%v个数据块\n", total_size, totalBlobNum)
		result.Set("totalBlobNum", totalBlobNum)
		blobNum := 1
		var lastblock bytes.Buffer
		for {
			glog.Cat("runtime").Printf("开始第%s次分割文件...,分割前容量%vM\n", strconv.Itoa(blobNum), float64(sdr.Len()/1024/1024))
			var err error
			if float64(blobNum) == totalBlobNum {
				_, err = lastblock.ReadFrom(sdr)
			} else {
				_, err = sdr.Read(block)
			}

			glog.Cat("runtime").Printf("分割后目标文件剩余容量%vM\n", float64(sdr.Len()/1024/1024))
			if err != nil {
				if err == io.EOF {
					glog.Cat("runtime").Println("分割完成并退出")
					break

				} else {

					glog.Cat("error").Error("分割失败并退出，失败原因", err.Error())
					return
				}

			}
			result.Set("blobNum", blobNum)
			fsdata_b, err := result.ToJson()
			if err != nil {
				glog.Cat("error").Error("任务--名称(%s)--数据库(%s)编码json格式失败，失败原因", task.Name, task.Database, err.Error())
				return
			}
			fsurls := "http://" + result.GetString("host") + "/Index/Slicetest?" + ghttp.BuildParams(map[string]string{
				"jsoninfo": string(fsdata_b),
			})

			var resps *http.Response
			var e error
			postimes1 := 0
		repost1:

			if float64(blobNum) == totalBlobNum {
				resps, e = http.Post(fsurls, "application/octet-stream", bytes.NewReader(lastblock.Bytes()))
			} else {
				resps, e = http.Post(fsurls, "application/octet-stream", bytes.NewReader(block))
			}
			if e != nil {
				if postimes1 < 5 {
					glog.Cat("error").Error("文件服务器未响应,5秒后重试....\n")
					time.Sleep(5 * time.Second)
					postimes1++
					glog.Cat("error").Errorf("任务--名称(%s)--数据库(%s)正在进行第%v次重新传输....\r\n", task.Name, task.Database, postimes1)
					goto repost1
				}
				glog.Cat("error").Errorf("任务--名称(%s)--数据库(%s)备份数据传输失败\r\n", task.Name, task.Database)
				return

			}
			defer resps.Body.Close()

			res_b, err := ioutil.ReadAll(resps.Body)
			glog.Cat("runtime").Printf("文件服务器返回信息%s\n", string(res_b))
			if err != nil {
				glog.Cat("error").Error("文件服务器返回信息出错，错误原因", err.Error())
				return
			}

			if err := gjson.DecodeTo(res_b, &resp); err != nil {

				glog.Cat("error").Errorf("解析出错，错误原因", err.Error())

				return
			}
			if resp.Code != 0 {
				glog.Cat("error").Errorf("任务--名称(%s)--数据库(%s)上传返回失败,返回内容,%v\r\n", task.Name, task.Database, resp)
				return
			}
			blobNum++

		}

	} else {
		fsdata_b, err := result.ToJson()

		if err != nil {
			glog.Cat("error").Error(err)
			return
		}

		fsurls := "http://" + result.GetString("host") + "/Index/ReceiveStream?" + ghttp.BuildParams(map[string]string{
			"jsoninfo": string(fsdata_b),
		})
		postimes := 0
	repost:
		resps, e := http.Post(fsurls, "application/octet-stream", bytes.NewReader(filedata))

		if e != nil {
			if postimes < 5 {
				glog.Cat("error").Error("文件服务器未响应,5秒后重试....\n")
				time.Sleep(5 * time.Second)
				postimes++
				glog.Cat("error").Errorf("任务--名称(%s)--数据库(%s)正在进行第%v次重新传输....\r\n", task.Name, task.Database, postimes)
				goto repost
			}
			glog.Cat("error").Errorf("任务--名称(%s)--数据库(%s)备份数据传输失败\r\n", task.Name, task.Database)
			return
		}
		defer resps.Body.Close()
		res_b, err := ioutil.ReadAll(resps.Body)
		if err != nil {
			glog.Cat("error").Error(e)
			return
		}
		if err := gjson.DecodeTo(res_b, &resp); err != nil {
			glog.Cat("error").Error(err)
			return
		}
		if resp.Code != 0 {
			glog.Cat("error").Errorf("任务--名称(%s)--数据库(%s)文件上传返回失败,返回内容,%v\r\n", task.Name, task.Database, resp)
			return
		}
	}

	server_data := make(map[string]interface{})
	fsfile_name := gjson.New(resp.Result).GetString("file_name")
	fsfile_dir := gjson.New(resp.Result).GetString("file_dir")
	server_data = datamap
	server_data["fs_file_size"] = len(filedata) / 1024
	server_data["fserver"] = result.GetString("host")
	server_data["dbfile"] = dbfile
	server_data["fs_file_name"] = fsfile_name
	server_data["fs_file_dir"] = fsfile_dir
	server_data["useruuid"] = usermap["uuid"]
	server_data["deviceuuid"] = GetHostUuid()
	server_data["dbname"] = task.Database
	server_data["keyfile"] = keyfile

	resp, e = Postserver(server+"/api/fileUpload/uploadsuccess", server_data)

	if e != nil {
		glog.Cat("error").Error(e)
		return
	}

}

/*
	删除客户端文件
*/
func RemoveClientFile(filepath string) {
	go func() {
		for i := 99; i > 0; i-- {
			if err := os.Remove(filepath); err == nil {
				break
			} else {
				glog.Cat("error").Error(err)
			}
			time.Sleep(5 * time.Second)
		}

	}()

}

/*
	获取最新登录的用户
*/
func GetLocalUser() map[string]interface{} {
	userdb := model.NewDB("user")
	users := userdb.SelectAll()
	if len(users) > 1 {
		//冒泡排序取最新登陆的用户
		for i := 0; i < len(users)-1; i++ {
			for j := i + 1; j < len(users); j++ {
				useri, _ := gjson.DecodeToJson(users[i])
				userj, _ := gjson.DecodeToJson(users[j])
				if useri.GetInt64("create_time") < userj.GetInt64("create_time") {
					users[i], users[j] = users[j], users[i]
				}
			}
		}
		user, _ := gjson.DecodeToJson(users[0])
		return user.ToMap()
	} else if len(users) == 1 {
		user, _ := gjson.DecodeToJson(users[0])
		return user.ToMap()
	} else {
		return nil
	}

}

/*
	获取文件校验码
*/
func getfilemd5(filepath string) string {
	if len(filepath) == 0 {
		return ""
	}
	file, err := os.Open(filepath)
	if err != nil {
		glog.Cat("error").Error(err)
	}
	fileinfo, _ := file.Stat()
	filedata := make([]byte, fileinfo.Size())
	_, err = file.Read(filedata)
	if err != nil {
		glog.Cat("error").Error(err)
	}
	has := md5.Sum(filedata)
	md5str := fmt.Sprintf("%x", has) //将[]byte转成16进制
	return md5str

}

/*
	获取数据校验码
*/
func getdatamd5(filedata []byte) string {
	if len(filedata) == 0 {
		return ""
	}
	has := md5.Sum(filedata)
	md5str := fmt.Sprintf("%x", has) //将[]byte转成16进制
	return md5str

}

/*
	数据压缩和加密
*/
func CompresswithCipher(filepath string) ([]byte, string, error) {
	//公钥获取
	rsa_b := model.NewDB("rsakey").WhereKey("public").Find()
	if len(rsa_b) == 0 {
		glog.Cat("error").Error("没有公钥,请先上传 或 生成秘钥")
		return []byte(""), "", errors.New("没有公钥,请先上传 或 生成秘钥")
	}
	rsa, err := gjson.DecodeToJson(rsa_b)
	if err != nil {
		glog.Cat("error").Error(err)
		return []byte(""), "", err
	}
	publickey := []byte(rsa.ToMap()["fdata"].(string))
	aes_key := []byte("youbeiyoubeiyoub")
	data, err := gziprsaes.CompresswithCipher(filepath, publickey, aes_key)
	if err != nil {

		glog.Cat("error").Error(err)
		return []byte(""), "", err
	}
	return data, rsa.ToMap()["fname"].(string), nil
}

/*
	文件解压还原
*/
func UnCompresswithCipher(file map[string]interface{}) bool {
	return gziprsaes.UnCompresswithCipher(file["sourcefile"].(string), file["filebyte"].([]byte), file["privatekey"].([]byte), file["downloadpath"].(string))
}

/*
	获取当前路径
*/
func GetCurrentDirectory() string {
	dir, err := filepath.Abs(filepath.Dir(os.Args[0])) //返回绝对路径  filepath.Dir(os.Args[0])去除最后一个元素的路径
	if err != nil {
		glog.Cat("error").Error(err)
	}
	return strings.Replace(dir, "\\", "/", -1) //将\替换成/
}

/*
	封装发送服务端
*/
func Postserver(url string, datamap map[string]interface{}) (Response, error) {
	var resp Response
	postimes := 0
LABEL:

	datamap_b, _ := gjson.Encode(datamap)
	r, e := ghttp.NewClient().Post(url, ghttp.BuildParams(map[string]string{
		"jsoninfo": string(datamap_b),
	}))
	if e != nil {
		if postimes < 5 {
			glog.Cat("error").Error("服务器未响应,5秒后重试...\r\n")
			time.Sleep(5 * time.Second)
			postimes++
			glog.Cat("error").Errorf("正在进行第%v次重试\r\n", postimes)
			goto LABEL
		}

		glog.Cat("error").Error("链接服务器失败\r\n")
		return resp, errors.New("服务器未响应,请稍后重试")
	}
	defer r.Close()
	gjson.DecodeTo(r.ReadAll(), &resp)

	if resp.Code == 1 && resp.Msg == "access_token is expired" {
		usermap := GetLocalUser()
		accesstoken, err := flushtoken(usermap["username"].(string), usermap["password"].(string))
		if err != nil {
			return resp, err
		} else {
			datamap["access_token"] = accesstoken
			goto LABEL
		}

	}
	return resp, e

}

/*
	获取设备Uuid
*/
func GetHostUuid() string {
	hostuuid := model.NewDB("host").WhereKey("uuid").Find()
	if len(hostuuid) <= 0 {
		newhostuuid := []byte(Uniquehost())
		model.NewDB("host").WhereKey("uuid").Data(newhostuuid).Insert()
		return string(newhostuuid)
	}
	return string(hostuuid)
}

/*
	删除任务
*/
func Deletetask(taskuuid string) (bool, error) {
	if model.NewDB("task").WhereKey(taskuuid).Delete() {
		gcron.Remove(taskuuid)
		return true, nil
	} else {
		return true, errors.New("删除失败")
	}
}

/*
	刷新TOKEN
*/
func flushtoken(un, up string) (string, error) {
	var resp Response
	user := make(map[string]string)
	user["username"] = un
	user["password"] = up
	user_b, err := gjson.Encode(user)
	if err != nil {
		return "", err
	}
	r, e := ghttp.NewClient().Post(server+"/api/logic/login", ghttp.BuildParams(map[string]string{
		"jsoninfo": string(user_b),
	}))

	if e != nil {
		glog.Cat("error").Error(e)

		return "", e
	}
	defer r.Close()
	gjson.DecodeTo(r.ReadAll(), &resp)

	if resp.Code == 0 {
		userinfo := gjson.NewUnsafe(resp.Result)
		user["uuid"] = userinfo.GetString("uuid")
		user["nickname"] = userinfo.GetString("nickname")
		user["level"] = userinfo.GetString("level")
		user["avatar"] = serverstatic + userinfo.GetString("avatar")
		user["access_token"] = userinfo.GetString("access_token")
		user["create_time"] = strconv.Itoa(userinfo.GetInt("create_time"))
		user["logout"] = strconv.Itoa(0)
		userinfo_b, err := gjson.Encode(&user)
		if err != nil {
			glog.Cat("error").Error(err)
			return "", err
		}
		model.NewDB("user").WhereKey(userinfo.GetString("uuid")).Data(userinfo_b).Update()
		return userinfo.GetString("access_token"), nil
	} else {
		return "", errors.New(resp.Msg)
	}

}

/*
	邮件发送
*/
func sendmail(to, data string) { //content-type 参数可加，此处省略
	smtp := gsmtp.New("smtp.exmail.qq.com:25", "发送者", "授权码")
	smtp.SendMail("发送者", to, "柚备数据安全", data, "html")
}
